﻿using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using System.Xml.Linq;
using LINQPad.Extensibility.DataContext;
using Microsoft.Win32;
using LINQPadDriverHelper;

namespace LINQPadDriver
{
  /// <summary>
  /// Interaction logic for ConnectionDialog.xaml
  /// </summary>
  internal sealed partial class ConnectionDialog
  {
    #region Nested type: FactoryItem

    private class FactoryItem
    {
      public MethodInfo Method { get; set; }

      public override string ToString()
      {
        return string.Format("{0}.{1}", Method.DeclaringType.FullName, Method.Name);
      }

      public FactoryItem(MethodInfo method)
      {
        Method = method;
      }
    }

    #endregion

    private readonly IConnectionInfo _cxInfo;

    #region Non-public methods

    private void BrowseAssembly(object sender, RequestNavigateEventArgs e)
    {
      var dialog = new OpenFileDialog {Title = "Choose custom assembly", DefaultExt = ".dll"};

      if (dialog.ShowDialog()==true)
        Assemblies.Text += (Assemblies.Text=="" ? "" : "\r\n") + dialog.FileName;
      e.Handled = true;
    }

    private void BrowseConfig(object sender, RequestNavigateEventArgs e)
    {
      var dialog = new OpenFileDialog {Title = "Choose config file", DefaultExt = ".config"};

      if (dialog.ShowDialog()==true)
        ConfigPath.Text = dialog.FileName;
      e.Handled = true;
    }

    private void ConfigPathChanged(object sender, TextChangedEventArgs e)
    {
      try {
        DomainNames.ItemsSource = null;
        XDocument doc = XDocument.Load(ConfigPath.Text);
        string[] names =
          doc.Elements("configuration").Elements("Xtensive.Orm").Elements("domains").Elements("domain").
            Attributes(
              "name").Select(a => a.Value).ToArray();
        DomainNames.ItemsSource = names;
      }
      catch (Exception ex) {
        MessageBox.Show("Error loading domain names:" + ex);
      }
    }

    private void OkClick(object sender, RoutedEventArgs e)
    {
      DialogResult = true;
      _cxInfo.DriverData = new XElement(
        "data",
        new XElement("Assemblies", Assemblies.Text.Trim().Replace("\r\n", Path.PathSeparator.ToString())),
        new XElement("Mode", ConfigFile.IsChecked.GetValueOrDefault() ? BuildMode.FromConfig : BuildMode.FromFactory),
        new XElement("DomainName", DomainNames.SelectedValue));
      if (Factories.SelectedItem!=null) {
        var item = (FactoryItem) Factories.SelectedItem;
        _cxInfo.DriverData.Add(
          new XElement(
            "Factory",
            new XElement("Assembly", item.Method.DeclaringType.Assembly.FullName),
            new XElement("Type", item.Method.DeclaringType.FullName),
            new XElement("Method", item.Method.Name)));
      }
      _cxInfo.DisplayName = DisplayName.Text;
      _cxInfo.AppConfigPath = ConfigPath.Text;
    }

    private void UpdateFactories()
    {
      Factories.ItemsSource = null;
      string[] assembliesPaths = Assemblies.Text.Split(new[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries);
      string[] paths = assembliesPaths.Select(Path.GetDirectoryName).Distinct().ToArray();
      ResolveEventHandler handler = (sender, args) => {
        var name = new AssemblyName(args.Name);
        var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name==name.Name);
        if (assembly != null)
          return assembly;
        return
          paths.Select(a => Path.Combine(a, new AssemblyName(args.Name).Name)).SelectMany(
            a => new[] {a + ".dll", a + ".exe"}).Where(File.Exists).Select(a=>Assembly.Load(File.ReadAllBytes(a))).
            FirstOrDefault();
      };
      AppDomain.CurrentDomain.AssemblyResolve +=
        handler;
      Factories.ItemsSource = (from a in assembliesPaths
        from type in Assembly.LoadFrom(a).GetTypes()
        from method in type.GetMethods()
        where method.IsStatic && method.ReturnType.FullName=="Xtensive.Orm.Domain" && method.GetParameters().Length==0
        select new FactoryItem(method)).ToArray();

      AppDomain.CurrentDomain.AssemblyResolve -= handler;
    }

    #endregion

    public ConnectionDialog(IConnectionInfo cxInfo)
    {
      _cxInfo = cxInfo;
      InitializeComponent();
      if (_cxInfo.DriverData==null)
        return;
      XElement assemblies = _cxInfo.DriverData.Element("Assemblies");
      if (assemblies!=null)
        Assemblies.Text = assemblies.Value.Replace(Path.PathSeparator.ToString(), "\r\n").Trim();
      DisplayName.Text = _cxInfo.DisplayName;
      ConfigPath.Text = _cxInfo.AppConfigPath;
      UpdateFactories();
      var modeElement = cxInfo.DriverData.Element("Mode");
      if (modeElement != null) {
        var mode = (BuildMode) Enum.Parse(typeof (BuildMode), (string) modeElement);
        if (mode==BuildMode.FromConfig) {
          ConfigFile.IsChecked = true;
          DomainNames.SelectedValue = (string) cxInfo.DriverData.Element("DomainName");
        }
        else {
          DomainFactory.IsChecked = true;
          XElement factory = cxInfo.DriverData.Element("Factory");
          var assembly = (string) factory.Element("Assembly");
          var type = (string) factory.Element("Type");
          var method = (string) factory.Element("Method");
          string s = type + "." + method;
          Factories.SelectedItem = Factories.ItemsSource.Cast<FactoryItem>().FirstOrDefault(a => a.ToString()==s);
        }
      }
    }

    private void Assemblies_TextChanged(object sender, TextChangedEventArgs e)
    {
      UpdateFactories();
    }
  }
}